﻿
// ScConnectorDlg.cpp: 实现文件
//

#include "pch.h"
#include "framework.h"
#include "ScConnector.h"
#include "ScConnectorDlg.h"
#include "afxdialogex.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

#define SC_FILE_NAME L"starcraft.exe"
#define CONFIG_FILE_NAME L"config.ini"
#define DDRAW_CONFIG_FILE_NAME L"ddraw.ini"
#define SC_HOOK_DLL_NAME L"ScHook.dll"

// 用于应用程序“关于”菜单项的 CAboutDlg 对话框

class CAboutDlg : public CDialogEx
{
public:
	CAboutDlg();

// 对话框数据
#ifdef AFX_DESIGN_TIME
	enum { IDD = IDD_ABOUTBOX };
#endif

	protected:
	virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV 支持

// 实现
protected:
	DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialogEx(IDD_ABOUTBOX)
{
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialogEx::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
END_MESSAGE_MAP()


// CScConnectorDlg 对话框



CScConnectorDlg::CScConnectorDlg(CWnd* pParent /*=nullptr*/)
	: CDialogEx(IDD_SCCONNECTOR_DIALOG, pParent)
{
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CScConnectorDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialogEx::DoDataExchange(pDX);
	DDX_Control(pDX, IDC_EDIT_SC_EXE_PATH, mEditScExePath);
	DDX_Control(pDX, IDC_EDIT_SC_SERVER_DOMAIN, mEditScServerDomain);
	DDX_Control(pDX, IDC_EDIT_SC_SERVER_PORT, mEditScServerPort);
	DDX_Control(pDX, IDC_RADIO_SET_WINDOW_MODE, mBtnSetWindowMode);
	DDX_Control(pDX, IDC_RADIO_SET_FULL_SCREEN, mBtnSetFullScreen);
	DDX_Control(pDX, IDC_CHECK_WINDOW_AUTO_ADAPT, mCheckWindowAutoAdapt);
	DDX_Control(pDX, IDC_EDIT_CUSTOM_WINDOW_WIDTH, mEditCustomWindowWidth);
	DDX_Control(pDX, IDC_EDIT_CUSTOM_WINDOW_HEIGHT, mEditCustomWindowHeight);
}

BEGIN_MESSAGE_MAP(CScConnectorDlg, CDialogEx)
	ON_WM_SYSCOMMAND()
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	ON_BN_CLICKED(IDC_BUTTON_EXPLORE, &CScConnectorDlg::OnBnClickedButtonExplore)
	ON_BN_CLICKED(IDC_BUTTON_LAUNCH, &CScConnectorDlg::OnBnClickedButtonLaunch)
	ON_BN_CLICKED(IDC_RADIO_SET_WINDOW_MODE, &CScConnectorDlg::OnBnClickedRadioSetWindowMode)
	ON_BN_CLICKED(IDC_RADIO_SET_FULL_SCREEN, &CScConnectorDlg::OnBnClickedRadioSetFullScreen)
	ON_BN_CLICKED(IDC_CHECK_WINDOW_AUTO_ADAPT, &CScConnectorDlg::OnBnClickedCheckWindowAutoAdapt)
END_MESSAGE_MAP()


// CScConnectorDlg 消息处理程序

BOOL CScConnectorDlg::OnInitDialog()
{
	CDialogEx::OnInitDialog();

	// 将“关于...”菜单项添加到系统菜单中。

	// IDM_ABOUTBOX 必须在系统命令范围内。
	ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
	ASSERT(IDM_ABOUTBOX < 0xF000);

	CMenu* pSysMenu = GetSystemMenu(FALSE);
	if (pSysMenu != nullptr)
	{
		BOOL bNameValid;
		CString strAboutMenu;
		bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
		ASSERT(bNameValid);
		if (!strAboutMenu.IsEmpty())
		{
			pSysMenu->AppendMenu(MF_SEPARATOR);
			pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
		}
	}

	// 设置此对话框的图标。  当应用程序主窗口不是对话框时，框架将自动
	//  执行此操作
	SetIcon(m_hIcon, TRUE);			// 设置大图标
	SetIcon(m_hIcon, FALSE);		// 设置小图标

	this->ReadSettingFromConfigFile();

	ShowWindow(SW_NORMAL);

	// TODO: 在此添加额外的初始化代码

	return TRUE;  // 除非将焦点设置到控件，否则返回 TRUE
}

void CScConnectorDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
	if ((nID & 0xFFF0) == IDM_ABOUTBOX)
	{
		CAboutDlg dlgAbout;
		dlgAbout.DoModal();
	}
	else
	{
		if (nID == SC_CLOSE)
		{
			this->WriteSettingToConfigFile();
		}
		CDialogEx::OnSysCommand(nID, lParam);
	}
}

// 如果向对话框添加最小化按钮，则需要下面的代码
//  来绘制该图标。  对于使用文档/视图模型的 MFC 应用程序，
//  这将由框架自动完成。

void CScConnectorDlg::OnPaint()
{
	if (IsIconic())
	{
		CPaintDC dc(this); // 用于绘制的设备上下文

		SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

		// 使图标在工作区矩形中居中
		int cxIcon = GetSystemMetrics(SM_CXICON);
		int cyIcon = GetSystemMetrics(SM_CYICON);
		CRect rect;
		GetClientRect(&rect);
		int x = (rect.Width() - cxIcon + 1) / 2;
		int y = (rect.Height() - cyIcon + 1) / 2;

		// 绘制图标
		dc.DrawIcon(x, y, m_hIcon);
	}
	else
	{
		CDialogEx::OnPaint();
	}
}

//当用户拖动最小化窗口时系统调用此函数取得光标
//显示。
HCURSOR CScConnectorDlg::OnQueryDragIcon()
{
	return static_cast<HCURSOR>(m_hIcon);
}

void CScConnectorDlg::ReadSettingFromConfigFile()
{
	TCHAR szWorkDir[MAX_PATH], buf[MAX_PATH];
	if (GetCurrentDirectory(MAX_PATH, szWorkDir) == 0) {
		TRACE("GetCurrentDirectory fail.");
		return;
	}
	_stprintf_s(this->szConfigFilePath, MAX_PATH, L"%s\\%s", szWorkDir, CONFIG_FILE_NAME);
	_stprintf_s(this->szDDrawConfigFilePath, MAX_PATH, L"%s\\%s", szWorkDir, DDRAW_CONFIG_FILE_NAME);
	_stprintf_s(this->szScHookDllPath, MAX_PATH, L"%s\\%s", szWorkDir, SC_HOOK_DLL_NAME);
	
	GetPrivateProfileString(L"running", L"sc_exe_path", L"", buf, sizeof(buf) / sizeof(TCHAR), this->szConfigFilePath);
	this->mEditScExePath.SetWindowText(buf);

	GetPrivateProfileString(L"running", L"sc_server_domain", L"", buf, sizeof(buf) / sizeof(TCHAR), this->szConfigFilePath);
	this->mEditScServerDomain.SetWindowText(buf);

	GetPrivateProfileString(L"running", L"sc_server_port", L"", buf, sizeof(buf) / sizeof(TCHAR), this->szConfigFilePath);
	this->mEditScServerPort.SetWindowText(buf);

	int mode = GetPrivateProfileInt(L"window", L"mode", 1, this->szConfigFilePath);
	bool isWindowMode = (mode == 1);
	this->mBtnSetWindowMode.SetCheck(isWindowMode);
	this->mBtnSetFullScreen.SetCheck(!isWindowMode);

	int autoAdapt = GetPrivateProfileInt(L"window", L"auto_adapt", 1, this->szConfigFilePath);
	this->mCheckWindowAutoAdapt.SetCheck(autoAdapt == 1);

	TCHAR customWidth[16], customHeight[16];
	GetPrivateProfileString(L"window", L"custom_width", L"800", customWidth, sizeof(customWidth) / sizeof(TCHAR), this->szConfigFilePath);
	GetPrivateProfileString(L"window", L"custom_height", L"600", customHeight, sizeof(customHeight) / sizeof(TCHAR), this->szConfigFilePath);
	this->mEditCustomWindowWidth.SetWindowText(customWidth);
	this->mEditCustomWindowHeight.SetWindowText(customHeight);
	this->mEditCustomWindowWidth.EnableWindow(!autoAdapt);
	this->mEditCustomWindowHeight.EnableWindow(!autoAdapt);
}

void CScConnectorDlg::WriteSettingToConfigFile()
{
	TCHAR buf[MAX_PATH] = { 0 };

	this->mEditScExePath.GetWindowText(buf, sizeof(buf) / sizeof(TCHAR));
	WritePrivateProfileString(L"running", L"sc_exe_path", buf, this->szConfigFilePath);

	this->mEditScServerDomain.GetWindowText(buf, sizeof(buf) / sizeof(TCHAR));
	WritePrivateProfileString(L"running", L"sc_server_domain", buf, this->szConfigFilePath);

	this->mEditScServerPort.GetWindowText(buf, sizeof(buf) / sizeof(TCHAR));
	WritePrivateProfileString(L"running", L"sc_server_port", buf, this->szConfigFilePath);

	_stprintf_s(buf, sizeof(buf) / sizeof(TCHAR), L"%d", this->mBtnSetWindowMode.GetCheck());
	WritePrivateProfileString(L"window", L"mode", buf, this->szConfigFilePath);

	_stprintf_s(buf, sizeof(buf) / sizeof(TCHAR), L"%d", this->mCheckWindowAutoAdapt.GetCheck());
	WritePrivateProfileString(L"window", L"auto_adapt", buf, this->szConfigFilePath);

	this->mEditCustomWindowWidth.GetWindowText(buf, sizeof(buf) / sizeof(TCHAR));
	WritePrivateProfileString(L"window", L"custom_width", buf, this->szConfigFilePath);

	this->mEditCustomWindowHeight.GetWindowText(buf, sizeof(buf) / sizeof(TCHAR));
	WritePrivateProfileString(L"window", L"custom_height", buf, this->szConfigFilePath);
}

void CScConnectorDlg::SetDDrawConfig()
{
	if (this->mBtnSetWindowMode.GetCheck() == 0) {
		WritePrivateProfileString(L"ddraw", L"fullscreen", TEXT("true"), this->szDDrawConfigFilePath);
		return;
	}

	TCHAR buf[16] = { 0 };
	if (this->mCheckWindowAutoAdapt.GetCheck() == 0) {
		this->mEditCustomWindowWidth.GetWindowText(buf, sizeof(buf) / sizeof(TCHAR));
		WritePrivateProfileString(L"ddraw", L"width", buf, this->szDDrawConfigFilePath);

		this->mEditCustomWindowHeight.GetWindowText(buf, sizeof(buf) / sizeof(TCHAR));
		WritePrivateProfileString(L"ddraw", L"height", buf, this->szDDrawConfigFilePath);
	} else {
		int screenWidth = GetSystemMetrics(SM_CXSCREEN);
		int screenHeight = GetSystemMetrics(SM_CYSCREEN);
		
		int height = screenHeight - 100; // 因为有窗口标题栏以及任务栏挡住，所以适当减少高度
		int width = height / 3 * 4;
		
		if (width > screenWidth) {
			width = screenWidth;
		}
		_stprintf_s(buf, sizeof(buf) / sizeof(TCHAR), L"%d", width);
		WritePrivateProfileString(L"ddraw", L"width", buf, this->szDDrawConfigFilePath);

		_stprintf_s(buf, sizeof(buf) / sizeof(TCHAR), L"%d", height);
		WritePrivateProfileString(L"ddraw", L"height", buf, this->szDDrawConfigFilePath);
	}
	WritePrivateProfileString(L"ddraw", L"fullscreen", TEXT("false"), this->szDDrawConfigFilePath);
}

void CScConnectorDlg::OnBnClickedButtonExplore()
{
	OPENFILENAME ofn;       // common dialog box structure
	TCHAR szFile[MAX_PATH];       // buffer for file name
	
	// Initialize OPENFILENAME
	ZeroMemory(&ofn, sizeof(ofn));
	ofn.lStructSize = sizeof(OPENFILENAME);
	ofn.hwndOwner = this->m_hWnd; // owner window
	ofn.lpstrFile = szFile;
	// Set lpstrFile[0] to '\0' so that GetOpenFileName does not 
	// use the contents of szFile to initialize itself.
	ofn.lpstrFile[0] = L'\0';
	ofn.nMaxFile = MAX_PATH;
	ofn.lpstrFilter = L"*.exe\0";
	ofn.nFilterIndex = 1;
	ofn.lpstrFileTitle = NULL;
	ofn.nMaxFileTitle = 0;
	ofn.lpstrInitialDir = NULL;
	ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;

	if (GetOpenFileName(&ofn) == TRUE) {
		this->mEditScExePath.SetWindowText(ofn.lpstrFile);
	}
}


void CScConnectorDlg::OnBnClickedButtonLaunch()
{
	TCHAR buf[MAX_PATH];
	this->mEditScExePath.GetWindowText(buf, MAX_PATH);

	if (StrStr(buf, SC_FILE_NAME) == NULL) {
		MessageBox(TEXT("请选择正确的星际主程序文件！"), TEXT("提示"), MB_ICONEXCLAMATION);
		return;
	}

	this->WriteSettingToConfigFile();
	this->SetDDrawConfig();

	EnableDebugPrivilege();

	PROCESS_INFORMATION processInfo;
	if (!StartNewProcess(buf, NULL, &processInfo)) {
		TRACE("StartNewProcess fail\n");
		return;
	}
	TRACE(TEXT("StartNewProcess success\n"));
	
	DWORD dllBaseAddr;
	if (InjectDll(&processInfo, this->szScHookDllPath, &dllBaseAddr)) {
		TRACE("InjectDllToSc success, dllBaseAddr: %X\n", dllBaseAddr);

		ScServerConfig svrCfg;
		this->mEditScServerDomain.GetWindowText(buf, MAX_PATH);
		StrCpyN(svrCfg.domain, buf, sizeof(svrCfg.domain) / sizeof(TCHAR));
		this->mEditScServerPort.GetWindowText(buf, MAX_PATH);
		StrCpyN(svrCfg.port, buf, sizeof(svrCfg.port) / sizeof(TCHAR));
		if (CallRemoteFunction(&processInfo, dllBaseAddr,
			this->szScHookDllPath, "setScServerConfig", (const LPVOID)&svrCfg, sizeof(ScServerConfig))) {
			ResumeThread(processInfo.hThread);
			TRACE("ResumeThread success\n");
		} else {
			TRACE("CallRemoteFunction failed\n");
		}
	}
	else {
		MessageBox(TEXT("注入dll失败，请用管理员权限启动程序！"), TEXT("提示"), MB_ICONEXCLAMATION);
	}

}


void CScConnectorDlg::OnBnClickedRadioSetWindowMode()
{
	this->mCheckWindowAutoAdapt.EnableWindow(TRUE);

	int autoAdapt = this->mCheckWindowAutoAdapt.GetCheck();
	this->mEditCustomWindowWidth.EnableWindow(!autoAdapt);
	this->mEditCustomWindowHeight.EnableWindow(!autoAdapt);
}


void CScConnectorDlg::OnBnClickedRadioSetFullScreen()
{
	this->mCheckWindowAutoAdapt.EnableWindow(FALSE);

	this->mEditCustomWindowWidth.EnableWindow(FALSE);
	this->mEditCustomWindowHeight.EnableWindow(FALSE);
}


void CScConnectorDlg::OnBnClickedCheckWindowAutoAdapt()
{
	int autoAdapt = this->mCheckWindowAutoAdapt.GetCheck();
	this->mEditCustomWindowWidth.EnableWindow(!autoAdapt);
	this->mEditCustomWindowHeight.EnableWindow(!autoAdapt);
}

